/*
 * Copyright (c) 2012 Tomas Shestakov. <http://code.google.com/p/jmmo/>
 */

package jmmo.engine.property

/**
 * A Special `Property` which bound to special [[jmmo.engine.property.Binding]] class.
 * `BoundProperty` value equals `Binding.apply` value and
 * changing any of `Binding.dependencies` properties cause `BoundProperty` to recalculate its value.
 * If parent `Property` value changes `LinkedProperty` value changes too and event posted.
 * For instances creation it is recommended to use `LinkedProperty` or `LinkedPropertyAttr` object.
 * @param newBinding special [[jmmo.engine.property.Binding]] class to bound the `Property`
 * @param id unique identifier of `Property`
 * @tparam A type of `Property` value
 * @author Tomas Shestakov
 */
class BoundProperty[A](newBinding: Binding[A] = null, val id: Identifier = Identifier.empty) extends PropertySupport[A] {
  this.binding = newBinding

  def isWritable = false

  def isReadable = binding ne null

  def apply() =
    if (!isReadable) throw new UnsupportedOperationException(this + " not bound")
    else value

  def update(value: A) {
    throw new UnsupportedOperationException(this + " is read only")
  }

  private[this] var b: Binding[A] = null

  /**
   * @return special [[jmmo.engine.property.Binding]] class to bound the `Property`
   */
  def binding = b

  /**
   * Sets specified bind to new binding object
   * @param newBinding special [[jmmo.engine.property.Binding]] class to bound the `Property`
   */
  def binding_=(newBinding: Binding[A]) {
    if (binding ne null) binding.dependencies foreach (_.removeChangeListener(changeListener))

    b = newBinding

    if (binding ne null) {
      binding.dependencies foreach (_.addChangeListener(changeListener))
      value = binding()
    }
  }

  /**
   * Contains the `Property` value
   */
  protected var value: A = _

  /**
   * Listener to listen dependencies values changes
   */
  protected val changeListener: ChangeListeners.Listener = {
    case event: ChangePropertyEvent[_] =>
      val oldValue = value
      value = binding()
      fireChangeEvent(ChangePropertyEvent(this, oldValue, value))
  }

  override def canEqual(other: Any) = other.isInstanceOf[BoundProperty[A]]

  override def equals(other: Any) = other match {
    case that: BoundProperty[A] => (that canEqual this) && id == that.id && binding == that.binding
    case _ => false
  }

  override def hashCode =
    41 * (
      41 + id.##
    ) + binding.##

  override def toString = "BoundProperty(" + binding + "," + "," + id + ")"

  def alter = jmmo.util.emptyPartial[Alteration]
}

/**
 * Contains factory methods to create instances of [[jmmo.engine.property.BoundProperty]].
 * Extractor method `unapply` extract only Property `id` and `bind` without `attributes`.
 */
object BoundProperty {

  /**
   * Creates a `BoundProperty` with specified initial bind, id and attributes.
   * @param binding special [[jmmo.engine.property.Binding]] class to bound the `Property`
   * @param id unique identifier of `Property`
   * @tparam A type of `Property` value
   */
  def apply[A](binding: Binding[A] = null, id: Identifier = Identifier.empty) = new BoundProperty[A](binding, id)

  /**
   * This extractor method is called in a pattern match { case BoundProperty(...) => }.
   * @param property
   * @tparam A type of `Property` value
   * @return Property `id` and `bind` wrapped in an Option.
   */
  def unapply[A](property: BoundProperty[A]): Option[(Binding[A], Identifier)] =
    if (property eq null) None
    else Some(property.binding, property.id)
}
